22. Strategy


Posted by WayneCheng on 2021-01-29

關於 Strategy 本篇將討論以下幾個問題

1. 關於 Strategy

2. UML

3. 將 UML 轉為程式碼

4. 情境


測試環境:

OS:Windows 10
IDE:Visual Studio 2019


1. 關於 Strategy

Define a family of algorithms, encapsulate each one, and make them interchangeable. Strategy lets the algorithm vary independently from clients that use it.

by Gang of Four

  • 定義並封裝一系列演算法,並使它們可切換
  • 策略模式使演算法可依據呼叫端指定而抽換

Strategy(策略)屬於行為型(Behavioral Patterns),當遇到一段程式其中有部分邏輯可由外部指定 時,可藉由 Strategy 將邏輯拆分成獨立 class,之後新增邏輯時只需新增 Strategy 實作即可。

優點:

  • 符合 開閉原則(Open Closed Principle)

缺點:

  • 所有策略實作都需要暴露至呼叫端

2. UML

Class 間關聯:

  • Context 可包含 Strategy
  • ConcreteStrategy A/B/C 繼承 Strategy

Class:

  • Context:使用演算法的物件
  • Strategy:策略的抽象類別或介面
  • ConcreteStrategy:策略實作

3. 將 UML 轉為程式碼

策略介面

/// <summary>
/// 策略介面
/// </summary>
public interface IStrategy
{
    public void AlgorithmInterface();
}

策略實作 A

/// <summary>
/// 策略實作 A
/// </summary>
public class ConcreteStrategyA : IStrategy
{
    public void AlgorithmInterface()
    {
        Console.WriteLine("Called ConcreteStrategyA.AlgorithmInterface()");
    }
}

策略實作 B

/// <summary>
/// 策略實作 B
/// </summary>
public class ConcreteStrategyB : IStrategy
{
    public void AlgorithmInterface()
    {
        Console.WriteLine("Called ConcreteStrategyB.AlgorithmInterface()");
    }
}

策略實作 C

/// <summary>
/// 策略實作 C
/// </summary>
public class ConcreteStrategyC : IStrategy
{
    public void AlgorithmInterface()
    {
        Console.WriteLine("Called ConcreteStrategyC.AlgorithmInterface()");
    }
}

使用演算法的物件

/// <summary>
/// 使用演算法的物件
/// </summary>
public class Context
{
    private IStrategy _strategy;

    public Context(IStrategy strategy)
    {
        _strategy = strategy;
    }

    public void ContextInterface()
    {
        _strategy.AlgorithmInterface();
    }
}
  1. 建立context並指定策略實作 A / B / C
  2. 呼叫策略實作 A / B / C
static void Main(string[] args)
{
    Default.Context context;

    context = new Default.Context(new Default.ConcreteStrategyA());
    context.ContextInterface();

    context = new Default.Context(new Default.ConcreteStrategyB());
    context.ContextInterface();

    context = new Default.Context(new Default.ConcreteStrategyC());
    context.ContextInterface();

    Console.ReadLine();
}

執行結果

Called ConcreteStrategyA.AlgorithmInterface()
Called ConcreteStrategyB.AlgorithmInterface()
Called ConcreteStrategyC.AlgorithmInterface()

4. 情境

我們接到了一個使用者在線上商城可自行選擇折扣方案的需求

  • 同時有多種折扣方案時,使用者可自行選定一種

策略介面

/// <summary>
/// 策略介面
/// </summary>
public interface IStrategy
{
    public int Discount(int amount);
}

策略實作,拆分各種計算結帳金額邏輯

/// <summary>
/// 結帳金額九折
/// </summary>
public class TenPercentOff : IStrategy
{
    public int Discount(int amount)
    {
        Console.WriteLine("結帳金額九折");
        return (int)(amount * 0.9);
    }
}

/// <summary>
/// 滿千折百
/// </summary>
public class ThousandGet100CashBack : IStrategy
{
    public int Discount(int amount)
    {
        Console.WriteLine("滿千折百");
        return amount >= 1000 ? amount - 100 : amount;
    }
}

/// <summary>
/// 送贈品
/// </summary>
public class Giveaway : IStrategy
{
    public int Discount(int amount)
    {
        Console.WriteLine("送贈品");

        return amount;
    }
}

依據使用者選擇折扣計算結帳金額

/// <summary>
/// 依據使用者選擇折扣計算結帳金額
/// </summary>
public class GetPrice
{
    private IStrategy _strategy;

    public GetPrice(IStrategy strategy)
    {
        _strategy = strategy;
    }

    public void Calculate(int amount)
    {
        Console.WriteLine($"原價:{amount}");

        var newAmount = _strategy.Discount(amount);

        Console.WriteLine($"折扣價:{newAmount}");
    }
}
  1. 建立context並選定折扣
  2. 輸入結帳金額計算折扣
static void Main(string[] args)
{
    Situation.GetPrice context;

    context = new Situation.GetPrice(new Situation.TenPercentOff());
    context.Calculate(1600);
    Console.WriteLine("\n");
    context = new Situation.GetPrice(new Situation.ThousandGet100CashBack());
    context.Calculate(1100);
    Console.WriteLine("\n");
    context = new Situation.GetPrice(new Situation.Giveaway());
    context.Calculate(600);

    Console.ReadLine();
}

執行結果

原價:1600
結帳金額九折
折扣價:1440

原價:1100
滿千折百
折扣價:1000

原價:600
送贈品
折扣價:600

完整程式碼

GitHub:Behavioral_09_Strategy


總結

在初期策略數量少且不知道未來會不會增加時使用 Strategy 會提高程式複雜度,建議直接使用 Delegates 來傳入,等到策略數量增加時再使用 Strategy 重構即可。


參考資料

  1. Design Patterns
  2. 大話設計模式
  3. dofactory
  4. Refactoring.Guru

新手上路,若有錯誤還請告知,謝謝


#designpattern #CSharp







Related Posts

Git change specific commit message

Git change specific commit message

從製作 visfest 2019 badge 認識 ObservableHQ

從製作 visfest 2019 badge 認識 ObservableHQ

『Android』ADB Debug by wifi 用Wifi連線來Debug

『Android』ADB Debug by wifi 用Wifi連線來Debug


Comments